﻿using System;
using System.Collections.Generic;
using RoMBot.MemoryLib;

namespace RoMBot.Classes
{
    public static class MemoryTable
    {
        public static Dictionary<int, int> IdAddressTables = new Dictionary<int, int>();
        private static Dictionary<int, string> _idNames = new Dictionary<int, string>();
        const int LineSize = 0x20;
        private static readonly uint TablePointer;
        private static readonly int[] StartAddressOffsets = new[] { 0, Addresses.tableStartPtrOffset, Addresses.tableDataStartPtrOffset };
        private static readonly int DataPointer;

        static MemoryTable()
        {
            TablePointer = Memory.ReadUIntPtr( Addresses.tablesBase, Addresses.tablesBaseOffset );
            DataPointer = ( int ) Memory.ReadUIntPtr( TablePointer, StartAddressOffsets ) - LineSize;
        }

        public static int GetIdAddressLine( int id )
        {
            var idTableHeader = 0;

            //Reads into the table to get a 'IdTableHeader' address
            for ( var i = 0; i < 10; i++ )
            {
                var offset0 = Memory.ReadUInt( ( uint ) ( DataPointer - LineSize * i ) );
                var offset8 = Memory.ReadUInt( ( uint ) ( DataPointer - LineSize * i ) + 0x8 );
                if ( ( offset0 > 0 || offset8 > 0 ) && offset0 == offset8 )
                {
                    idTableHeader = ( int ) offset0;
                    break;
                }
            }

            if ( idTableHeader == 0 )
            {
                //throw new Exception( "Unable to find a valid IdTableAddress" );
                return 0;
            }

            // If is it already in the table, return it, else find it in memory.
            return IdAddressTables.ContainsKey( id ) ? IdAddressTables[ id ] : FindIdAddressLine( idTableHeader, id );
        }

        private static int FindIdAddressLine( int idTableHeader, int idToFind )
        {
            //Get information from the 'IdTableHeader'
            var smallestIdAddress = Memory.ReadUInt( idTableHeader );
            var middleIdAddress = Memory.ReadUInt( idTableHeader + 0x4 );
            var largestIdAdress = Memory.ReadUInt( idTableHeader + 0x8 );

            var smallestId = Memory.ReadInt( ( int ) smallestIdAddress + Addresses.idOffset );
            var largestId = Memory.ReadInt( ( int ) largestIdAdress + Addresses.idOffset );

            // Check the 'IdToFind'.
            if ( idToFind == 0 || idToFind < smallestId || idToFind > largestId )
            {
                return 0;
            }

            // Use the middleIdAddress from IdAddressTables to start search
            var checkDataPointer = ( int ) middleIdAddress;
            var loopTest = 0; // Used to make sure it doesn't get stuck in a loop if bad id
            int currentId;
            do
            {
                loopTest = loopTest + 1;

                // Get 0 and 8 offsets
                var offset8 = Memory.ReadUInt( checkDataPointer + 8 );
                // Read a valid value? Has it reached the end of the table?
                if ( offset8 == 0 || offset8 < 0x100000 )
                {
                    return 0;
                }
                var offset0 = Memory.ReadUInt( checkDataPointer );

                // Get the id
                currentId = Memory.ReadInt( checkDataPointer + Addresses.idOffset );

                //printf("currentAddress %X , current ID %X:%d IdToFind %X:%d\n",dataPointer,currentId,currentId,IdToFind,IdToFind)

                // Check the id.
                if ( currentId == 0 || currentId < smallestId || currentId > largestId )
                {
                    return 0;
                }

                // Add to table to speed future searches
                if ( !IdAddressTables.ContainsKey( currentId ) )
                {
                    IdAddressTables[ currentId ] = checkDataPointer;
                }

                // Id not found. Get next dataPointer.
                if ( currentId > idToFind )
                {
                    if ( offset0 == offset8 || offset0 == idTableHeader )
                    {
                        checkDataPointer += LineSize;
                    }
                    else
                    {
                        checkDataPointer = ( int ) offset0;
                    }
                }
                else if ( currentId < idToFind )
                {
                    if ( offset0 == offset8 || offset8 == idTableHeader )
                    {
                        checkDataPointer -= LineSize;
                    }
                    else
                    {
                        checkDataPointer = ( int ) offset8;
                    }
                }
            } while ( currentId != idToFind && loopTest < 500 );

            return checkDataPointer;
        }

        public static int GetItemAddress( int id )
        {
            if ( id > 0 )
            {
                var addressline = GetIdAddressLine( id );
                if ( addressline > 0 )
                {
                    var address = (int) Memory.ReadUIntPtr( addressline + 0x10, 0x8 );
                    if ( address == 0 )
                    {
                        // Item data not substanciated yet. Do "GetCraftRequestItem", then the address will exist.
                        Helpers.RoMScript( "GetCraftRequestItem(" + id + ", -1)" );
                        address = (int) Memory.ReadUIntPtr( addressline + 0x10, 0x8 );
                    }
                    return address;
                }
                if ( Bot.DebugItems )
                {
                    Bot.Warning( string.Format( "Id not found: {0}.", id ) );
                }
            }
            return 0;
        }

        public static string GetIdName( int itemId )
        {
            if ( itemId > 0 )
            {
                lock ( _idNames )
                {
                    if ( !_idNames.ContainsKey( itemId ) )
                    {
                        var itemAddress = GetItemAddress( itemId );
                        if ( itemAddress > 0 )
                        {
                            var name = Memory.ReadUtf8StringPtr( itemAddress + Addresses.nameOffset, 0 );
                            if ( string.IsNullOrEmpty( name ) )
                            {
                                // Item data not totally substanciated yet. Do "GetCraftRequestItem", then the address will exist.
                                Helpers.RoMScript( "GetCraftRequestItem(" + itemId + ", -1)" );
                                name = Memory.ReadUtf8StringPtr( itemAddress + Addresses.nameOffset, 0 );
                            }
                            if ( !string.IsNullOrEmpty( name ) )
                            {
                                _idNames.Add( itemId, name );
                            }
                            return name;
                        }
                    }
                    else
                    {
                        return _idNames[ itemId ];
                    }
                }
            }
            return null;
        }
    }

}
